#ifdef __linux__

#include <GL/gl.h>
#include <GL/glx.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/extensions/xf86vmode.h>

#include "../common/xp.h"

typedef struct {
	Display *display;
	int screen;
	Window win;
	GLXContext ctx;
	XSetWindowAttributes attr;
	Bool fullscreen;
	Bool doubleBuffered;
	XF86VidModeModeInfo deskMode;
	int x, y;
	unsigned int width, height;
	unsigned int depth;
} State;

State xp_state;

XP_KEY_CALLBACK xp_keyboard_callback;
XP_MOUSE_CALLBACK xp_mouse_callback;
XP_RESIZE_CALLBACK xp_resize_callback;

static int attrListSgl[] = { GLX_RGBA, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4,
		GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, None };

static int attrListDbl[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 4,
		GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, None };

bool xp_open(char* title, int width, int height, bool fullscreen) {
	XVisualInfo *vi;
	Colormap cmap;
	int dpyWidth, dpyHeight;
	int i;
	XF86VidModeModeInfo **modes;
	int modeNum;
	int bestMode;
	Atom wmDelete;
	Window winDummy;
	unsigned int borderDummy;

	xp_state.fullscreen = fullscreen;

	// set best mode to current
	bestMode = 0;
	xp_state.display = XOpenDisplay(0);
	xp_state.screen = DefaultScreen(xp_state.display);

#ifndef MINIMAL
	int vidModeMajorVersion, vidModeMinorVersion;
	XF86VidModeQueryVersion(xp_state.display, &vidModeMajorVersion, &vidModeMinorVersion);
	printf("XF86VidModeExtension-Version %d.%d\n", vidModeMajorVersion, vidModeMinorVersion);
#endif

	XF86VidModeGetAllModeLines(xp_state.display, xp_state.screen, &modeNum, &modes);
	/* save desktop-resolution before switching modes */
	xp_state.deskMode = *modes[0];
	/* look for mode with requested resolution */
	for (i = 0; i < modeNum; i++) {
		if ((modes[i]->hdisplay == width) && (modes[i]->vdisplay == height)) {
			bestMode = i;
		}
	}
	/* get an appropriate visual */
	vi = glXChooseVisual(xp_state.display, xp_state.screen, attrListDbl);
	xp_state.doubleBuffered = True;
	if (vi == NULL) {
		vi = glXChooseVisual(xp_state.display, xp_state.screen, attrListSgl);
		xp_state.doubleBuffered = False;
		PRINT_ERROR("Only Singlebuffered Visual!\n");
	}

#ifndef MINIMAL
	int glxMajorVersion, glxMinorVersion;
	glXQueryVersion(xp_state.display, &glxMajorVersion, &glxMinorVersion);
	printf("glX-Version %d.%d\n", glxMajorVersion, glxMinorVersion);
#endif

	/* create a GLX context */
	xp_state.ctx = glXCreateContext(xp_state.display, vi, 0, GL_TRUE);

	/* create a color map */
	cmap = XCreateColormap(xp_state.display, RootWindow(xp_state.display, vi->screen), vi->visual, AllocNone);
	xp_state.attr.colormap = cmap;
	xp_state.attr.border_pixel = 0;

	if (xp_state.fullscreen) {
		XF86VidModeSwitchToMode(xp_state.display, xp_state.screen, modes[bestMode]);
		XF86VidModeSetViewPort(xp_state.display, xp_state.screen, 0, 0);
		dpyWidth = modes[bestMode]->hdisplay;
		dpyHeight = modes[bestMode]->vdisplay;

#ifndef MINIMAL
		printf("Resolution %dx%d\n", dpyWidth, dpyHeight);
#endif

		XFree(modes);

		/* create a fullscreen window */
		xp_state.attr.override_redirect = True;
		xp_state.attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask;
		xp_state.win = XCreateWindow(xp_state.display, RootWindow(xp_state.display, vi->screen),
				0, 0, dpyWidth, dpyHeight, 0, vi->depth, InputOutput,
				vi->visual,
				CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect,
				&xp_state.attr);
		XWarpPointer(xp_state.display, None, xp_state.win, 0, 0, 0, 0, 0, 0);
		XMapRaised(xp_state.display, xp_state.win);
		XGrabKeyboard(xp_state.display, xp_state.win, True, GrabModeAsync, GrabModeAsync,
				CurrentTime);
		XGrabPointer(xp_state.display, xp_state.win, True, ButtonPressMask, GrabModeAsync,
				GrabModeAsync, xp_state.win, None, CurrentTime);
	} else {
		/* create a window in window mode*/
		xp_state.attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask;
		xp_state.win = XCreateWindow(xp_state.display, RootWindow(xp_state.display, vi->screen),
				0, 0, width, height, 0, vi->depth, InputOutput, vi->visual,
				CWBorderPixel | CWColormap | CWEventMask, &xp_state.attr);
		/* only set window title and handle wm_delete_events if in windowed mode */
		wmDelete = XInternAtom(xp_state.display, "WM_DELETE_WINDOW", True);
		XSetWMProtocols(xp_state.display, xp_state.win, &wmDelete, 1);
		XSetStandardProperties(xp_state.display, xp_state.win, title, title, None, NULL,
				0, NULL);
		XMapRaised(xp_state.display, xp_state.win);
	}
	/* connect the glx-context to the window */
	glXMakeCurrent(xp_state.display, xp_state.win, xp_state.ctx);
	XGetGeometry(xp_state.display, xp_state.win, &winDummy, &xp_state.x, &xp_state.y,
			&xp_state.width, &xp_state.height, &borderDummy, &xp_state.depth);

	if (!glXIsDirect(xp_state.display, xp_state.ctx)) {
		PRINT_ERROR("Sorry, no Direct Rendering possible!\n");
	}
	return true;
}

bool xp_open_fullscreen(char* title, int width, int height) {
	return xp_open(title, width, height, true);
}

bool xp_open_windowed(char* title, int width, int height) {
	return xp_open(title, width, height, false);
}

int xp_close() {
	if (xp_state.ctx) {
		if (!glXMakeCurrent(xp_state.display, None, NULL)) {
			printf("Could not release drawing context.\n");
		}
		glXDestroyContext(xp_state.display, xp_state.ctx);
		xp_state.ctx = NULL;
	}
	/* switch back to original desktop resolution if we were in fs */
	if (xp_state.fullscreen) {
		XF86VidModeSwitchToMode(xp_state.display, xp_state.screen, &xp_state.deskMode);
		XF86VidModeSetViewPort(xp_state.display, xp_state.screen, 0, 0);
	}
	XCloseDisplay(xp_state.display);
	return 0;
}

void xp_register_keyboard_callback(XP_KEY_CALLBACK callback) {
	xp_keyboard_callback = callback;
}

void xp_register_mouse_callback(XP_MOUSE_CALLBACK callback) {
	xp_mouse_callback = callback;
}

void xp_register_resize_callback(XP_RESIZE_CALLBACK callback) {
	xp_resize_callback = callback;
}

int xp_handle_events() {
	XEvent event;
	int retcode;
	while (XPending(xp_state.display) > 0) {
		XNextEvent(xp_state.display, &event);
		// http://www.tronche.com/gui/x/xlib/events/processing-overview.html
		switch (event.type) {
		case ButtonPress:
			break;
		case KeyPress:
			if (xp_keyboard_callback != NULL)
				retcode = xp_keyboard_callback(XLookupKeysym(&event.xkey, 0) & 0xff, true);
			break;
		case KeyRelease:
			if (xp_keyboard_callback != NULL)
				retcode = xp_keyboard_callback(XLookupKeysym(&event.xkey, 0) & 0xff, false);
			break;
		case ConfigureNotify:
			if (xp_resize_callback != NULL) {
				xp_state.width = event.xconfigure.width;
				xp_state.height = event.xconfigure.height;
				xp_resize_callback(xp_state.width, xp_state.height);
			}
			break;
		case DestroyNotify:
			retcode = XP_SHUTDOWN;
			break;
		case ResizeRequest:
			break;
		case ClientMessage:
			if (*XGetAtomName(xp_state.display, event.xclient.message_type) ==  *"WM_PROTOCOLS") {
				retcode = XP_SHUTDOWN;
			}
			break;
		}

		if (retcode != XP_NO_OP) {
			return retcode;
		}
	}

	return XP_NO_OP;
}

#endif

